package com.lifesting.tool.j2as3.visitor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.util.IModifierConstants;

import com.lifesting.tool.j2as3.action.Util;
import com.lifesting.tool.j2as3.visitor.SubTypeVisitor.SubTypeDescriptor;

public class TypeRelatioShipVisitor extends ASTVisitor {
	//Method key
	private HashSet<String> overrideMethod = new HashSet<String>();
	//Need set pointer to parent container
	//Subclass Qn
	private HashSet<String> needSetPointer = new HashSet<String>();
	
	private Map<String, SubTypeDescriptor> subTypeDescriptors;
	private String[] rootPackages;
	protected HashMap<String,HashSet<String>> resolvedSignature = new HashMap<String, HashSet<String>>();
	protected HashSet<String> methodSignatures = new HashSet<String>();
	
	public static final String SUPER_TYPES = "SUPER_TYPES";
	public static final String[] NO_SUPERTYPES = new String[0];
	public Set<String> getOverrideMethod() {
		return Collections.unmodifiableSet(overrideMethod);
	}
	public Set<String> getNeedSetPointer() {
		return Collections.unmodifiableSet(needSetPointer);
	}
	public HashMap<String, HashSet<String>> getResolvedSignature() {
		return resolvedSignature;
	}
	public void setSubTypeDescriptors(
			Map<String, SubTypeDescriptor> subTypeDescriptors) {
		this.subTypeDescriptors = subTypeDescriptors;
		filterInterface();
	}
	public void setRootPackages(String[] rootPackages) {
		this.rootPackages = rootPackages;
	}
	private void filterInterface() {
		for (Iterator<String> keyIterator = subTypeDescriptors.keySet().iterator(); keyIterator.hasNext(); )
		{
			String qn = keyIterator.next();
			SubTypeDescriptor sd = subTypeDescriptors.get(qn);
			if (!sd.isInterface){
				//All subclass (filter interface)
				needSetPointer.add(qn);
			}
		}
	}
	public void reset(){
		resolvedSignature.clear();
	}
	private CompilationUnit unit;

	@Override
	public void endVisit(CompilationUnit node) {
		unit = null;
	}
	@Override
	public boolean visit(CompilationUnit node) {
		unit = node;
		for (Object o : node.types()){
			((TypeDeclaration)o).accept(this);
		}
		return false;
	}
	@Override
	public void endVisit(MethodDeclaration node) {
		super.endVisit(node);
	}
	@Override
	public void endVisit(TypeDeclaration node) {
		resolvedSignature.put(Util.getName(node), methodSignatures);
	}
	@Override
	public boolean visit(MethodDeclaration node) {
		int flags = node.getModifiers();
		if ((flags & IModifierConstants.ACC_ABSTRACT)!=0) return false;
		IMethodBinding method_binding = node.resolveBinding();
		if (method_binding == null) return false;
		IMethod method = (IMethod) method_binding.getJavaElement();
		//Check whether it was defined in super type
		String[] super_types = (String[]) node.getParent().getProperty(SUPER_TYPES);
		String methodSignature = Util.makeOverrideSignatures(method);
		for (String s : super_types){
			HashSet<String> all_ss = resolvedSignature.get(s);
			if (all_ss != null && all_ss.contains(methodSignature))
				overrideMethod.add(method.getKey());
		}
		if (Util.overridable(method)){
			methodSignatures.add(methodSignature);
		}
		return false;
	}
	@Override
	public boolean visit(TypeDeclaration node) {
		if (node.isInterface()) return false;
		methodSignatures = new HashSet<String>();
		node.setProperty(SUPER_TYPES, buildInherient(node));
		Util.setName(node, node.resolveBinding().getQualifiedName());
		shrink(node);
		List only_types = node.bodyDeclarations();
		for (Object o : only_types){
			if (o instanceof TypeDeclaration){
				((TypeDeclaration)o).accept(this);
			}
			if (o instanceof MethodDeclaration && canVisit(((MethodDeclaration)o).getModifiers())){
				((MethodDeclaration)o).accept(this);
			}
		}
		return false;
	}
	//Test a method can be a overridable one.
	private boolean canVisit(int modifiers) {
		if ((modifiers & IModifierConstants.ACC_STATIC) != 0) return false;
		return true;
	}
	private void shrink(TypeDeclaration node) {
		String[] chain = (String[]) node.getProperty(SUPER_TYPES);
		boolean found_in_head = false;
		for (int i = chain.length-1; i>=0; i--){
			if (!found_in_head)
				found_in_head = subTypeDescriptors.containsKey(chain[i]);
			else
				needSetPointer.remove(chain[i]);
		}
		if (found_in_head){
			needSetPointer.remove(Util.getName(node));
		}
	}
	private String[] buildInherient(TypeDeclaration node) {
		Type sp = node.getSuperclassType();
		if (sp == null) return NO_SUPERTYPES;
		ITypeBinding binding = sp.resolveBinding();
		List<String> super_types = new ArrayList<String>();
		while (binding != null){
			String qn = binding.getQualifiedName();
			if (!containType(qn)) break;
			super_types.add(qn);
			if (!resolvedSignature.containsKey(qn))
			{
				collectOverridableMethod(binding);
			}
			binding = binding.getSuperclass();
		}
		return super_types.toArray(new String[super_types.size()]);
	}
	private void collectOverridableMethod(ITypeBinding binding) {
		IType javaelement = (IType) binding.getJavaElement();
		if (javaelement == null) return;
		HashSet<String> signs = new HashSet<String>();
		try {
			IMethod[] methods = javaelement.getMethods();
			for (IMethod m : methods){
				if (Util.overridable(m)){
					signs.add(Util.makeOverrideSignatures(m));
				}
			}
			resolvedSignature.put(binding.getQualifiedName(), signs);
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		
	}


	protected boolean containType(String p_name) {
		if (p_name == null) return false;
		for (String s : rootPackages) {
			if (p_name.startsWith(s)) {
				return true;
			}
		}
		return false;
	}
	
}
